﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Sims3Package.Wrapper;
using Sims3Package.Interface;
using Sims3Package;
using System.Reflection;
using System.IO;
using PackageEditor.Properties;
using System.Collections;
using PackageEditor.Helper;

namespace PackageEditor
{
    public partial class MainForm : Form
    {
        private string filename = string.Empty;
        public static Package pack = null;
        private Dictionary<UInt32, Type> WrapperLists = null;
        FilterPanel filterPanel;
        private bool isFilterShown = false;
        private static bool packChanged = false;

        public static Dictionary<UInt32, String> TagMap
        {
            get
            {
                return ResourceForm.nameMap;
            }
        }

        public static Dictionary<UInt32, string> ExtentionMap
        {
            get
            {
                Dictionary<UInt32, string> map = new Dictionary<uint, string>();
                using (StringReader sr = new StringReader(Resources.Extensions))
                {
                    string s;
                    while ((s = sr.ReadLine()) != null)
                    {

                        if (s.StartsWith(";") || s.StartsWith("*")) continue;
                        string[] type = s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                        if (type.Length < 2) continue;

                        map.Add(Convert.ToUInt32(type[0], 16), type[2]);
                    }
                }
                return map;
            }
        }

        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            // Load Wrappers

            WrapperLists = new Dictionary<uint, Type>();
            Assembly assy = Assembly.LoadFile(Path.Combine(Application.StartupPath, @"Sims3Package.Wrapper.dll"));
            foreach (System.Type type in assy.GetTypes())
                if (type.BaseType == typeof(Wrapper))
                {
                    foreach (PropertyInfo info in type.GetProperties())
                    {
                        if (info != null && info.PropertyType == typeof(List<UInt32>))
                        {
                            List<UInt32> v = info.GetValue(null, null) as List<UInt32>;
                            if (v != null) foreach (UInt32 i in v) if (!this.WrapperLists.Keys.Contains(i)) this.WrapperLists.Add(i, type);
                        }
                    }
                }

            // Initialize List view
            lstEntries.Columns.Add("Name", -2, HorizontalAlignment.Center);
            lstEntries.Columns.Add("Tag", -2, HorizontalAlignment.Center);
            lstEntries.Columns.Add("Type", -2, HorizontalAlignment.Center);
            lstEntries.Columns.Add("Group", -2, HorizontalAlignment.Center);
            lstEntries.Columns.Add("Instance", -2, HorizontalAlignment.Center);

            // Disable buttons
            SetButton(false);

            // Hook the events
            addToolStripMenuItem.Click += toolStripButtonAdd_Click;
            editInHelperToolStripMenuItem.Click += toolStripButtonEdit_Click;
            lstEntries.MouseDoubleClick += detailToolStripMenuItem_Click;

            // Set filter panel;
            filterPanel = new FilterPanel(ref lstEntries);

            // Load Icon
            this.Icon = Properties.Resources.Icon;


        }

        #region File Control
        private void toolStripButtonOpen_Click(object sender, EventArgs e)
        {
            if (packChanged) Save(true);
            using (OpenFileDialog open = new OpenFileDialog())
            {
                open.Filter = "DBPF Package|*.package|Sims 3 World File|*.world|Save File|.nhd|Sims 3 Store Content|*.dbc|All File|*.*";
                open.ShowReadOnly = true;
                open.Multiselect = false;
                open.CheckFileExists = true;
                if (open.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    try
                    {
                        pack = new Package(open.FileName, !open.ReadOnlyChecked);
                        filename = open.FileName;
                        ClearList();
                        bgwRead.RunWorkerAsync(filename);
                        this.Text = "Reading...Pease Wait...";
                        SetButton(true);
                        ResetValue();
                    }
                    catch (Exception ex) { ExceptionBox.Show(ex.ToString()); }
                }
            }
            
            
        }

        private void toolStripButtonNew_Click(object sender, EventArgs e)
        {
            if (packChanged) Save(true);
            pack = new Package();
            ClearList();
            this.ValueBox.Text = "";
            SetButton(true);
            this.Text = "ProjectK: The Sims 3 Package Editor For Mono";
            packChanged = true;
            ResetValue();
        }

        public void ResetValue()
        {
            this.pictureBox.Visible = false;
            this.pictureBox.Image = null;
            this.ValueBox.Text = string.Empty;
            this.ValueBox.Visible = true;
        }

        private void toolStripButtonSave_Click(object sender, EventArgs e)
        {
            if (filename != string.Empty)
            {
                pack.Save();
            }
            else
            {
                Save(false);
            }
        }

        private void Save(bool promptToSave)
        {
            if (promptToSave)
            {
                if (MessageBox.Show("Your changes are unsaved. Do you want to save your package?", "Attention", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.No)
                { packChanged = false; return; }
            }
            using (SaveFileDialog save = new SaveFileDialog())
            {
                save.Filter = "DBPF Package|*.package|Sims 3 World File|*.world|Save File|.nhd|Sims 3 Store Content|*.dbc|All File|*.*";
                if (save.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    pack.SaveAs(save.FileName);
                    filename = save.FileName;
                    pack = new Package(save.FileName, true);
                    this.Text = string.Format("ProjectK: The Sims 3 Package Editor For Mono {0}", filename);
                    packChanged = false;
                }
            }
        }

        private void toolStripButtonSaveAs_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog save = new SaveFileDialog())
            {
                save.Filter = "DBPF Package|*.package|Sims 3 World File|*.world|Save File|.nhd|Sims 3 Store Content|*.dbc|All File|*.*";
                if (save.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    pack.SaveAs(save.FileName);
                    filename = save.FileName;
                    this.Text = string.Format("ProjectK: The Sims 3 Package Editor For Mono {0}", filename);
                    packChanged = false;
                }
            }
        }

        private void toolStripButtonAdd_Click(object sender, EventArgs e)
        {
            if (!this.toolStripButtonAdd.Enabled) return;
            foreach (Control c in this.Controls)
                if (c.GetType() == typeof(FilterPanel)) c.Dispose();
            ListViewItemEx item = new ListViewItemEx(new ResourceEntry(0, 0, 0));
            using (ResourceForm resourceform = new ResourceForm(true, ref item))
            {
                if (resourceform.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    if (pack.EntryList.Exists(entry => entry.ResourceType == item.ResourceType && entry.ResourceInstance == item.ResourceInstance
                        && entry.ResourceGroup == item.ResourceGroup))
                    {
                        if(MessageBox.Show("Duplicated resource already exists, do you want to replace it?", "Note:", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
                            != System.Windows.Forms.DialogResult.Yes) return;
                    }
                    MemoryStream s = new MemoryStream();
                    pack.AddResourceEntry(item.entry, s);
                    AddItemToList(ref item, true, ref lstEntries);
                    
                }
            }
        }

        private void toolStripButtonFilter_Click(object sender, EventArgs e)
        {
            //FilterPanel panel = new FilterPanel(ref lstEntries, ref pack);
            if (!this.Controls.Contains(filterPanel))
            {
                this.Controls.Add(filterPanel);
                filterPanel.Location = new Point(90, 25);
                filterPanel.BringToFront();
                DraggableControls.ControlExtension.Draggable(filterPanel, true);
            }
            if (!isFilterShown) { filterPanel.Show(); isFilterShown = true; } else { filterPanel.Hide(); isFilterShown = false; }

        }
        #endregion

        #region Functions

        #region UI Control
        void SetButton(bool value)
        {
            toolStripButtonAdd.Enabled = value;
            toolStripButtonEdit.Enabled = value;
            toolStripButtonFilter.Enabled = value;
            toolStripButtonSave.Enabled = value;
            toolStripButtonSaveAs.Enabled = value;
        }

        // Sorting in List View
        private int sortColumn = -1;

        private void lstEntries_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Determine whether the column is the same as the last column clicked.
            if (e.Column != sortColumn)
            {
                // Set the sort column to the new column.
                sortColumn = e.Column;
                // Set the sort order to ascending by default.
                lstEntries.Sorting = SortOrder.Ascending;
            }
            else
            {
                // Determine what the last sort order was and change it.
                if (lstEntries.Sorting == SortOrder.Ascending)
                    lstEntries.Sorting = SortOrder.Descending;
                else
                    lstEntries.Sorting = SortOrder.Ascending;
            }

            // Call the sort method to manually sort.
            lstEntries.Sort();
            // Set the ListViewItemSorter property to a new ListViewItemComparer
            // object.
            this.lstEntries.ListViewItemSorter = new ListViewItemComparer(e.Column,
                                                              lstEntries.Sorting);
        }
        class ListViewItemComparer : IComparer
        {
            private int col;
            private SortOrder order;
            public ListViewItemComparer()
            {
                col = 0; order = SortOrder.Ascending;
            }
            public ListViewItemComparer(int column, SortOrder order) 
            {
                col = column; this.order = order;
            }
            public int Compare(object x, object y)
            {
                int returnVal = -1;
                returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text, ((ListViewItem)y).SubItems[col].Text);
                if (order == SortOrder.Descending)
                    // Invert the value returned by String.Compare.
                    returnVal *= -1;
                return returnVal;
            }
        }

        #endregion

        private void ClearList()
        {
            this.lstEntries.Items.Clear();
        }

        private static void SetName(ref ListViewItemEx item, ref ListView listview)
        {
            if (item.Text == null || item.Text == string.Empty) return;
            if (pack.EntryList.Exists(entry => entry.ResourceType == 0x0166038CU))
            {
                ITGIblock block = pack.EntryList.Find(entry => entry.ResourceType == 0x0166038CU);
                NameMapResourceWrapper w = new NameMapResourceWrapper(pack.GetResourceStream(block));
                if (w.NameMap.ContainsKey(item.ResourceInstance)) { w.NameMap[item.ResourceInstance] = item.Text; }
                else { w.NameMap.Add(item.ResourceInstance, item.Text); }
                pack.ReplaceResource(block, w.UnParse());
            }
            else
            {
                NameMapResourceWrapper w = new NameMapResourceWrapper(null);
                w.NameMap.Add(item.ResourceInstance, item.Text);
                ResourceEntry tgi = new ResourceEntry(0x0166038CU, 0, 0, TGIOrder.TGI);
                
                pack.AddResourceEntry(tgi, w.UnParse(), true);
                ListViewItemEx exItem = new ListViewItemEx(tgi);
                AddItemToList(ref exItem, true, ref listview);
            }
        }

        internal static void AddItemToList(ref ListViewItemEx item ,bool add, ref ListView listview)
        {
            string temp = item.Text;
            item.SubItems.Clear();
            item.Text = temp;
            item.SubItems.AddRange(new string[]{TagMap.ContainsKey(item.ResourceType) ? TagMap[item.ResourceType] :string.Empty, string.Format("0x{0:X8}", item.ResourceType)
                , string.Format("0x{0:X8}", item.ResourceGroup)
                , string.Format("0x{0:X16}", item.ResourceInstance)});
            SetName(ref item, ref listview);
            if (add) listview.Items.Add(item);
            packChanged = true;
        }
        #endregion


        #region Event System
        public class ResourceChangeEventArgs : EventArgs
        {
            public ListViewItemEx Item { get; set; }
            public bool StreamChanged { get; set; }
            // true to add, otherwise
            public bool Add { get; set; }
            public ResourceChangeEventArgs(ref ListViewItemEx item, bool streamchanged) : base() { this.Item = item; this.StreamChanged = streamchanged; }
        }

        protected void OnPackageResourceChanged(object sender, ResourceChangeEventArgs e)
        {
            SetPackageResource(e.Item, e.StreamChanged);
        }

        private void UpdatePackageResource(ListViewItemEx item, bool streamchanged)
        {
            ResourceChangeEventArgs e = new ResourceChangeEventArgs(ref item, streamchanged);
            OnPackageResourceChanged(this, e);
        }

        private void SetPackageResource(ListViewItemEx item, bool streamchanged)
        {
            item.SubItems.Clear();
            if (streamchanged && item.ResourceStream != null) pack.ReplaceResource(item, item.ResourceStream, item.Compressed);
            item.SubItems.AddRange(new string[]{TagMap.ContainsKey(item.ResourceType) ? TagMap[item.ResourceType] :string.Empty, string.Format("0x{0:X8}", item.ResourceType)
                , string.Format("0x{0:X8}", item.ResourceGroup)
                , string.Format("0x{0:X16}", item.ResourceInstance)});
            lstEntries.Refresh();
        }
        public delegate void ResourceUpdateHandler(object sender, ResourceChangeEventArgs e);
        //public event ResourceUpdateHandler OnResourceUpdate;
        #endregion

        

        private void lstEntries_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lstEntries.SelectedItems.Count != 1) return;
            ListViewItemEx item = lstEntries.SelectedItems[0] as ListViewItemEx;
            if (item == null) return;
            if (WrapperLists.ContainsKey(item.ResourceType))
            {
                try
                {
                    System.Type type = WrapperLists[item.ResourceType];
                    
                    if (type != typeof(ImageResourceWrapper) && type!= typeof(DDSResourceWrapper))
                    {
                        Wrapper w = System.Activator.CreateInstance(type, pack.GetResourceStream(item.entry)) as Wrapper;
                        ValueBox.Visible = true;
                        ValueBox.Text = w.Value;
                        toolStripButtonEdit.Enabled = true;
                        pictureBox.Visible = false;
                        pictureBox.Image = null;
                        //// Test part
                        //ResourceHelper help = new ResourceHelper(w);
                        //try
                        //{
                        //    if (help.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                        //    {
                        //    }
                        //}
                        //catch (Exception ex) { MessageBox.Show(ex.ToString()); }
                    }
                    else if (type == typeof(DDSResourceWrapper))
                    {
                        DDSResourceWrapper w = new DDSResourceWrapper(pack.GetResourceStream(item));
                        pictureBox.Visible = true;
                        pictureBox.Image = w.DdsImage;
                        ValueBox.Text = string.Empty;
                        ValueBox.Visible = false;
                        toolStripButtonEdit.Enabled = true;
                    }
                    else
                    {
                        ImageResourceWrapper w = System.Activator.CreateInstance(type, pack.GetResourceStream(item.entry)) as ImageResourceWrapper;
                        pictureBox.Visible = true;
                        pictureBox.Image = w.ImageValue;
                        ValueBox.Text = string.Empty;
                        ValueBox.Visible = false;
                    }

                    editInHelperToolStripMenuItem.Enabled = true;
                    
                }
                catch (Exception ex)
                {
                    pictureBox.Visible = false;
                    pictureBox.Image = null;
                    ValueBox.Visible = true;
                    ValueBox.Text = ex.ToString();
                }
            }
            else { ValueBox.Text = string.Empty; toolStripButtonEdit.Enabled = false; editInHelperToolStripMenuItem.Enabled = false; ValueBox.Visible = true; pictureBox.Visible = false; }

        }

        

        private void bgwRead_DoWork(object sender, DoWorkEventArgs e)
        {
            
            if (pack == null) return;
            Dictionary<UInt64, string> nameMap = new Dictionary<ulong, string>();
            List<ListViewItemEx> items = new List<ListViewItemEx>();
            if (pack.EntryList.Exists(entry => entry.ResourceType == 0x0166038CU))
                foreach (IResourceEntry entry in pack.EntryList.FindAll(entry => entry.ResourceType == 0x0166038CU))
                {
                    NameMapResourceWrapper w = new NameMapResourceWrapper(pack.GetResourceStream(entry));
                    foreach (var kvp in w.NameMap) if (!nameMap.ContainsKey(kvp.Key)) nameMap.Add(kvp.Key, kvp.Value);
                }
            foreach (ResourceEntry entry in pack.EntryList)
            {
                string name = nameMap.Keys.Contains(entry.ResourceInstance) ? nameMap[entry.ResourceInstance] : string.Empty;
                ListViewItemEx item = new ListViewItemEx(entry, name);
                item.SubItems.AddRange(new string[]{TagMap.ContainsKey(item.ResourceType) ? TagMap[item.ResourceType] :string.Empty, string.Format("0x{0:X8}", item.ResourceType)
                , string.Format("0x{0:X8}", item.ResourceGroup)
                , string.Format("0x{0:X16}", item.ResourceInstance)});
                items.Add(item);
            }

            e.Result = items;
        }

        private void bgwRead_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

            List<ListViewItemEx> items = e.Result as List<ListViewItemEx>;
            if (items == null) return;
            this.Text = "Adding resource items";
            lstEntries.BeginUpdate();
            foreach (var entry in items) lstEntries.Items.Add(entry);
            //Parallel.ForEach(items, entry => lstEntries.Items.Add(entry));
            lstEntries.EndUpdate();
            items = null;
            this.Text = string.Format("ProjectK: The Sims 3 Package Editor For Mono {0}", filename);

        }

        private void contextMenuStrip_Opening(object sender, CancelEventArgs e)
        {
            bool value = pack != null;
            foreach (object obj in this.contextMenuStrip.Items)
            {
                ToolStripMenuItem item = obj as ToolStripMenuItem;
                if (item != null) item.Enabled = value;
            }
            if (value) { bool test = lstEntries.SelectedItems.Count > 0;  detailToolStripMenuItem.Enabled = test; exportToolStripMenuItem.Enabled = test;
            duplicateToolStripMenuItem.Enabled = test; deleteToolStripMenuItem.Enabled = test; editInHelperToolStripMenuItem.Enabled = toolStripButtonEdit.Enabled;
            }
            //this.Text = string.Format("ProjectK: The Sims 3 Package Editor For Mono {0}", filename);

        }

        private void detailToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lstEntries.SelectedItems.Count != 1) return;
            ListViewItemEx item = lstEntries.SelectedItems[0] as ListViewItemEx;
            if(item == null) return;
            ResourceForm resourceform = new ResourceForm(false, ref item);
            if (resourceform.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {

                AddItemToList(ref item, false, ref lstEntries);

            }
        }

        private void Export(ref ListViewItemEx item)
        {
            using (SaveFileDialog save = new SaveFileDialog())
            {
                string exten = ExtentionMap.ContainsKey(item.ResourceType) ? TagMap[item.ResourceType] + "|" + ExtentionMap[item.ResourceType] : "Unknown File|*.bnry";
                save.Filter = exten;
                save.FileName = string.Format("{0:X8}-{1:X8}-{2:X16}", item.ResourceType, item.ResourceGroup, item.ResourceInstance);
                if (save.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    using (FileStream s = new FileStream(save.FileName, FileMode.Create, FileAccess.Write))
                    {
                        pack.GetResourceStream(item.entry).CopyTo(s);
                    }
                }
            }
        }

        private void Import()
        {
            using (OpenFileDialog open = new OpenFileDialog())
            {
                open.Filter = "All Files|*.*";
                open.Multiselect = false;
                if (open.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    using(FileStream s = new FileStream(open.FileName, FileMode.Open, FileAccess.Read))
                    {
                        ListViewItemEx item = new ListViewItemEx(new ResourceEntry(0, 0, 0));
                        string name = open.SafeFileName;
                        name = name.Split('.')[0];
                        string[] types = name.Split('-');
                        if (types.Count() == 3)
                        {
                            if (ResourceForm.UInt32Parse(types[0]) && ResourceForm.UInt32Parse(types[1]) && ResourceForm.UInt64Parse(types[2]))
                            {
                                item.ResourceType = Convert.ToUInt32(types[0], 16); item.ResourceGroup = Convert.ToUInt32(types[1], 16); item.ResourceInstance = Convert.ToUInt64(types[2], 16);
                            }
                        }
                        using (ResourceForm resourceform = new ResourceForm(false, ref item))
                        {
                            if (resourceform.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                            {
                                MemoryStream stream = new MemoryStream();
                                s.CopyTo(stream);
                                stream.Position = 0;
                                pack.AddResourceEntry(item.entry);
                                AddItemToList(ref item, true, ref lstEntries);
                                pack.ReplaceResource(item, stream);
                            }
                        }
                        
                    }
                }
            }
        }

        private void exportToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lstEntries.SelectedItems.Count != 1) return;
            ListViewItemEx item = lstEntries.SelectedItems[0] as ListViewItemEx;
            if (item == null) return;
            Export(ref item);
        }

        private void importToolStripMenuItem_Click(object sender, EventArgs e)
        {
            
            Import();
        }

        private void toolStripButtonEdit_Click(object sender, EventArgs e)
        {
            if (lstEntries.SelectedItems.Count != 1) return;
            ListViewItemEx item = lstEntries.SelectedItems[0] as ListViewItemEx;
            if (item == null) return;

            // Avoid using reflection due to platform issues(maybe be, I'll test it as soon as I have access to a Mac and test it in reflection)
            try
            {
                if (TextResourceWrapper.ResourceTypes.Contains(item.ResourceType))
                {
                    using (TextHelper help = new TextHelper(pack.GetResourceStream(item)))
                    {
                        if (help.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                        {
                            pack.ReplaceResource(item, help.w.UnParse());
                            packChanged = true;
                            lstEntries.Items[lstEntries.SelectedIndices[0]] = lstEntries.SelectedItems[0];// to make visual change immediately
                            return;
                        }
                    }
                }
                if (S3SAResourceWrapper.ResourceTypes.Contains(item.ResourceType))
                {
                    using (S3SAHelper help = new S3SAHelper(pack.GetResourceStream(item)))
                    {
                        if (help.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                        {
                            pack.ReplaceResource(item, help.w.UnParse());
                            packChanged = true;
                            lstEntries.Items[lstEntries.SelectedIndices[0]] = lstEntries.SelectedItems[0];// to make visual change immediately
                            return;
                        }
                    }
                }
                if (STBLResourceWrapper.ResourceTypes.Contains(item.ResourceType))
                {
                    using (STBLHelper help = new STBLHelper(pack.GetResourceStream(item)))
                    {
                        if (help.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                        {
                            pack.ReplaceResource(item, help.w.UnParse());
                            packChanged = true;
                            lstEntries.Items[lstEntries.SelectedIndices[0]] = lstEntries.SelectedItems[0];// to make visual change immediately
                            return;
                        }
                    }
                }
            }
            catch (Exception ex) { ExceptionBox.Show(ex.ToString()); }
            

        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (packChanged) Save(true);
        }

        private void viewInHexToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lstEntries.SelectedItems.Count != 1) return;
            ListViewItemEx item = lstEntries.SelectedItems[0] as ListViewItemEx;
            if (item == null) return;

            HexHelper hex = new HexHelper(pack.GetResourceStream(item));
            hex.Show();
            
        }
        
        
        
        
    }
}
